package org.kwin.spring.context;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.kwin.spring.aop.annotation.Aspect;
import org.kwin.spring.aop.aspectj.ProxyFactory;
import org.kwin.spring.aop.aspectj.proxy.AspectProxy;
import org.kwin.spring.aop.aspectj.proxy.Proxy;
import org.kwin.spring.beans.annotation.*;
import org.kwin.spring.beans.factory.AbstractActionFactory;
import org.kwin.spring.beans.factory.BeanFactory;
import org.kwin.spring.transaction.TransactionProxy;
import org.kwin.spring.web.annotation.Controller;
import org.kwin.spring.web.handler.HandlerMapping;

/**
 * @author : wangkai
 * @version V1.0
 * @Package org.kwin.spring.context
 * @Description:
 * @date 2021年09月28日 9:52
 **/
public class ActionApplicationContext extends AbstractActionFactory {
    
    // 分隔符号
    public static final String SEPARATE;
    // 项目路径
    public static final String PROJECT_PATH;
    // 获取IOC容器的功能
    private BeanFactory beanFactory;
    // 代理对象容器
    private Map<String, Object> porxyMap = new HashMap<>();
    
    static
    {
        if (System.getProperty("os.name").startsWith("Windows"))
        {
            SEPARATE = "\\";
        } else
        {
            SEPARATE = "/";
        }
        PROJECT_PATH = System.getProperty("user.dir");
    }
    
    public Map<String, Object> getPorxyMap() {
        return porxyMap;
    }
    
    /**
     * 扫描并加载组件
     *
     * @param clzz
     * @param properties
     */
    @Override
    protected void scanComponent(Class clzz, Properties properties) {
    
        ComponentScan componentScan = (ComponentScan) clzz.getDeclaredAnnotation(ComponentScan.class);

        loadComponent(componentScan.value() , properties);
        
    }
    
    /**
     * 加载组件
     * @param basePkg
     * @param properties
     */
    private void loadComponent(String basePkg, Properties properties) {
        try {
            String path = basePkg.replaceAll("\\.", "/");
            
            Enumeration<URL> urls = this.getClass().getClassLoader().getResources(basePkg.replaceAll("\\.", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if(url == null) {
                    continue;
                }
                loadComponent(basePkg, url, properties);
                
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    
    }
    private void loadComponent(String basePkg, URL url, Properties properties) {
        if(url == null) {
            return;
        }
        String protocol = url.getProtocol();
        if("file".equalsIgnoreCase(protocol)) {
            String packagePath = url.getPath().replaceAll("%20", " ");
            loadComponentByPath(basePkg, packagePath, properties);
        }
        if("jar".equalsIgnoreCase(protocol)) {
            loadComponentByJar(url, properties);
        }
    }
    
    private void loadComponentByJar(URL url, Properties properties) {
        try {
            JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
            if (jarURLConnection == null) {
                return;
            }
            JarFile jarFile = jarURLConnection.getJarFile();
            if (jarFile == null) {
                return;
            }
            Enumeration<JarEntry> jarEntries = jarFile.entries();
            while (jarEntries.hasMoreElements()) {
                JarEntry jarEntry = jarEntries.nextElement();
                String jarEntryName = jarEntry.getName();
                if (jarEntryName.endsWith(".class")) {
                    String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("\\\\", ".").replaceAll("/", ".");
                    loadComponentByClzz(className, properties);
                }
            }
            
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private void loadComponentByPath(String basePkg, String pkgPath, Properties properties) {
        File[] files = new File(pkgPath).listFiles(file -> (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory());
        for(File file : files) {
            String fileName = file.getName();
            if(file.isDirectory()) {
                String childPkgPath = pkgPath + SEPARATE + fileName;
                loadComponentByPath(basePkg + "." + fileName, childPkgPath, properties);
            } else {
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                
               String packageName =  pkgPath.replaceAll("\\\\", ".").replaceAll("/", ".");
                className = basePkg + "." + className;
                loadComponentByClzz(className, properties);
            }
        }
        
    }
    
    private void loadComponentByClzz(String clzzName, Properties properties) {
        try {
            Class clzz = Class.forName(clzzName);
            if (clzz.isAnnotationPresent(Component.class) || clzz.isAnnotationPresent(Controller.class)
                || clzz.isAnnotationPresent(Service.class) || clzz.isAnnotationPresent(Configuration.class))
            {
                Object bean = loadComponent(clzz, properties);
                if (clzz.isAnnotationPresent(Controller.class))
                {
                    HandlerMapping.registerMapping(clzz, bean);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    private Object loadComponent(Class clzz,  Properties prop) {
        try
        {
            Object bean = clzz.getConstructor().newInstance();
            beanMap.put(clzz.getName(), bean);
            // 注入Value值
            Field[] fields = clzz.getDeclaredFields();
            for (Field f : fields)
            {
                if (f.isAnnotationPresent(Value.class))
                {
                    Value myValue = f.getAnnotation(Value.class);
                    f.setAccessible(true);
                    String value = myValue.value();
                    if (clzz.isAnnotationPresent(PropertySource.class))
                    {
                        if (value.startsWith("${") && value.endsWith("}"))
                        {
                            PropertySource propertySource = (PropertySource) clzz.getAnnotation(PropertySource.class);
                            Properties properties = new Properties();
                            properties.load(this.getClass().getClassLoader().getResourceAsStream(propertySource.value()));
                            value = value.substring(2, value.length() - 1);
                            String defaultVal = null;
                            if(value.contains(":")) {
                                defaultVal = value.substring(value.lastIndexOf(":") + 1);
                            }
                            f.set(bean, properties.getProperty(value, defaultVal));
                            continue;
                        }
                    }
                    if (value.startsWith("${") && value.endsWith("}")) {
                        value = value.substring(2, value.length() - 1);
                        String defaultVal = null;
                        if(value.contains(":")) {
                            defaultVal = value.substring(value.lastIndexOf(":") + 1);
                        }
                        value = prop.getProperty(value, defaultVal);
                    }
                
                    if (f.getType() == String.class || f.getType() == char.class || f.getType() == Character.class)
                    {
                        f.set(bean, value);
                    } else if (f.getType() == Integer.class || f.getType() == int.class)
                    {
                        f.set(bean, Integer.parseInt(value));
                    } else if (f.getType() == Double.class || f.getType() == double.class)
                    {
                        f.set(bean, Double.parseDouble(value));
                    } else if (f.getType() == Float.class || f.getType() == float.class)
                    {
                        f.set(bean, Float.parseFloat(value));
                    } else if (f.getType() == Long.class || f.getType() == long.class)
                    {
                        f.set(bean, Long.parseLong(value));
                    } else if (f.getType() == Short.class || f.getType() == short.class)
                    {
                        f.set(bean, Short.parseShort(value));
                    } else if (f.getType() == Byte.class || f.getType() == byte.class)
                    {
                        f.set(bean, Byte.parseByte(value));
                    } else if (f.getType() == Boolean.class || f.getType() == boolean.class)
                    {
                        f.set(bean, Boolean.parseBoolean(value));
                    }
                }
            }
        
            // 初始化方法级Bean
            Method[] methods = clzz.getDeclaredMethods();
            for (Method m : methods)
            {
                if (m.isAnnotationPresent(Bean.class))
                {
                    Object result = m.invoke(bean);
                    beanMap.put(result.getClass().getName(), result);
                    System.out.println("注入了一个方法级Bean=" + result.toString());
                }
            }
            return bean;
        } catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }
    
    /**
     * 装配组件
     *
     * @param beanFactory
     */
    @Override
    protected void autowiredComponent(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
        for (Object bean : beanMap.values())
        {
            autowiredComponent(bean);
        }
    }
    
    private void autowiredComponent(Object bean) {
        Class clzz = bean.getClass();
        Field[] fields = clzz.getDeclaredFields();
        for (Field field : fields) {
            if(field.isAnnotationPresent(Autowired.class)) {
                // 字段是否可以访问
                try {
                    field.setAccessible(true);
                    Object obj = beanFactory.getBean(field.getType());
                    if (obj == null) {
                        System.err.println("组件不存在， type is " + field.getType());
                    } else {
                        field.set(bean, obj);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /**
     * 加载切面类型的组件
     */
    @Override
    protected void loadAspect() {
        System.out.println("切面组件开始加载");
        // 切面-目标对象集合的映射
        Map<Object, Set<Object>> aspectMap = createAspectMap();

        // 目标对象-切面代理对象列表的映射
        Map<Object, List<Proxy>> targetMap = createTargetMap(aspectMap);
        
        for(Map.Entry<Object, List<Proxy>> targetEntry : targetMap.entrySet()) {
            Object targetObj = targetEntry.getKey();
            List<Proxy> proxyList = targetEntry.getValue();
            Object proxy = ProxyFactory.getInstance(targetObj, proxyList);
            porxyMap.put(targetObj.getClass().getName(), proxy);
        }
    }
    
    /**
     * 获取目标对象和代理类的映射
     * @param aspectMap
     * @return
     */
    private Map<Object, List<Proxy>> createTargetMap(Map<Object, Set<Object>> aspectMap) {
        Map<Object, List<Proxy>> targetMap = new HashMap<>();
        
        for (Map.Entry<Object, Set<Object>> proxyEntry : aspectMap.entrySet()) {
            Object aspectObj = proxyEntry.getKey();
    
            Set<Object> targetObjs = proxyEntry.getValue();
            if(aspectObj == TransactionProxy.class) {
                continue;
            }
            Proxy proxy = new AspectProxy(aspectObj);
            for(Object targetObj : targetObjs) {
                if(targetMap.containsKey(targetObj)) {
                    targetMap.get(targetObj).add(proxy);
                } else {
                    List<Proxy> aspectList = new ArrayList<Proxy>();
                    aspectList.add(proxy);
                    targetMap.put(targetObj, aspectList);
                }
            }
        }
    
        // 事务放到代理链的最后一层
        for (Map.Entry<Object, Set<Object>> proxyEntry : aspectMap.entrySet()) {
            Object aspectObj = proxyEntry.getKey();
        
            Set<Object> targetObjs = proxyEntry.getValue();
            Proxy proxy = null;
            if(aspectObj == TransactionProxy.class) {
                proxy = new TransactionProxy(beanFactory);
                for(Object targetObj : targetObjs) {
                    if(targetMap.containsKey(targetObj)) {
                        targetMap.get(targetObj).add(proxy);
                    } else {
                        List<Proxy> aspectList = new ArrayList<Proxy>();
                        aspectList.add(proxy);
                        targetMap.put(targetObj, aspectList);
                    }
                }
            }
        
        }
        return targetMap;
    }
    
    
    /**
     * 获取切面-目标对象集合的映射
     */
    private Map<Object, Set<Object>> createAspectMap() {
        Map<Object, Set<Object>> aspectMap = new HashMap<>();
        addAspectProxy(aspectMap);
        addTransactionProxy(aspectMap);
        return aspectMap;
    }
    
    
    private void addAspectProxy(Map<Object, Set<Object>> map) {
        for (Object obj : beanMap.values()) {
            if(obj.getClass().isAnnotationPresent(Aspect.class)) {
                Aspect aspect = obj.getClass().getAnnotation(Aspect.class);
                // 与该切面对应的目标对象集合
                Set<Object> objects = createTargetClassSet(aspect);
                map.put(obj, objects);
            }
        }
    }
    
    private void addTransactionProxy(Map<Object, Set<Object>> map) {
        Set<Object> set = null;
        for (Object obj : beanMap.values()) {
            if(obj.getClass().isAnnotationPresent(Service.class)) {
                set = new HashSet<>();
                set.add(obj);
            }
        }
        if(set != null && set.size() > 0) {
            map.put(TransactionProxy.class, set);
        }
    }
    
    private Set<Object> createTargetClassSet(Aspect aspect) {
        Set<Object> targetClassSet = new HashSet<Object>();
        // 包名
        String pkg = aspect.pkg();
        // 类名
        String cls = aspect.cls();
        // 如果包名与类名均不为空，则添加指定类
        if (!pkg.equals("") && !cls.equals("")) {
            targetClassSet.add(beanMap.get(pkg + "." + cls));
        } else if (!pkg.equals("")) {
            // 如果包名不为空, 类名为空, 则添加该包名下所有类
            targetClassSet.addAll(getSetByPath(pkg));
        }
        
        return targetClassSet;
    }
    
    private Set<Object> getSetByPath(String path) {
        Set<Object> target = new HashSet<>();
        for(String clzzName : beanMap.keySet()) {
            if(clzzName.startsWith(path)) {
                target.add(beanMap.get(clzzName));
            }
        }
        return target;
    }
    
    
    
    protected void autowiredProxy()
    {
        for (Object o : beanMap.values())
        {
            Field[] fields = o.getClass().getDeclaredFields();
            for (Field f : fields)
            {
                if (f.isAnnotationPresent(Autowired.class))
                {
                    autowiredProxy(f, o);
                }
            }
        }
    }
    
    private void autowiredProxy(Field f, Object bean)
    {
        Class<?> clazz = f.getType();
        System.out.println(this.porxyMap);
        
        String name = null;
        for (String key : beanMap.keySet())
        {
            Object o = beanMap.get(key);
            if (o.getClass() == clazz || o.getClass().getSuperclass() == clazz)
            {
                name = key;
            } else
            {
                for (Class c : o.getClass().getInterfaces())
                {
                    if (c == clazz)
                    {
                        name = key;
                        break;
                    }
                }
            }
        }
        if (name != null)
        {
            try
            {
                if (this.porxyMap.containsKey(name))
                {
                    f.setAccessible(true);
                    f.set(bean, this.porxyMap.get(name));
                }
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}
